package com.t.bricks.bussiness.server.login.impl;

import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.t.bricks.bussiness.db.dao.login.LoginDao;
import com.t.bricks.bussiness.model.login.LoginUser;
import com.t.bricks.bussiness.server.login.LoginServer;
import com.t.bricks.model.Log;
import com.t.bricks.model.MsgEmity;
import com.t.bricks.utils.function.data.IntegerUtil;
import com.t.bricks.utils.function.encode.MD5Util;
import com.t.bricks.utils.function.file.FileUtil;

@Service
public class LoginServerImpl implements LoginServer {
	
	@Autowired
	private LoginDao loginDao;
	
	/**
	 * md5加密干扰串
	 */
	@Value("${app.md5.key:未设置app.md5.key配置项}")
	private String appMd5Key;
	
	/**
	 * 超时时间(秒)
	 */
	@Value("${server.session.timeout:9000}")
	private String serverSessionTimeout;
	
	/**
	 * 超时时间(毫秒)
	 */
	private long iTimeOut = -1;
	
	/**
	 * 存储用户登录信息的集合
	 * 注意:ConcurrentHashMap是线程安全的
	 */
	private static Map<String, LoginUser> mapLoginUser = new ConcurrentHashMap<String, LoginUser>();
	
	/**
	 * 最后清理的时间戳
	 * 提示:时间戳如1581825404040
	 */
	private static long dLastClearTime = 0;

	/**
	 * 是否禁用文本缓存
	 */
	private static Boolean isDisableTxtCache = null;
	
	/**
	 * 缓存文件夹路径
	 */
	private static String sCacheFilePath = String.format("%s/temp/cache/Login/",
			System.getProperty("user.dir").replaceAll("\\\\", "/"));
	
	/**
	 * 读取缓存文件
	 */
	@Autowired
	private void readCache() {
		if (null != isDisableTxtCache && isDisableTxtCache) {
			return;
		}
		
		String filePath = String.format("%sLoginCache.txt", sCacheFilePath);
		MsgEmity me = FileUtil.readFromFile(filePath);
		if (!me.isSuccess()) {
			return;
		}
		
		String json = me.getData();
		if (null == json || "".equals(json.trim())) {
			return;
		}
		
		TypeReference<Map<String, LoginUser>> typeRef = new TypeReference<Map<String, LoginUser>>() {};
   
		ObjectMapper mapper = new ObjectMapper();
		try {
			Map<String, LoginUser> map = mapper.readValue(json, typeRef);
			for (Map.Entry<String, LoginUser> entity : map.entrySet()) {
				mapLoginUser.put(entity.getKey(), entity.getValue());
			}
		} catch (Exception e) {
			Log.error("读取用户登录缓存发生异常：", e.getMessage());
		}
	}
	
	/**
	 * 登录
	 * @param sNameOrNo
	 * @param sPass
	 * @return
	 */
	@Override
	public MsgEmity in(String sNameOrNo, String sPass) {
		checkOutTime();//检查超时的登录用户,超时则剔除集合
		
		if (null == sNameOrNo || "".equals(sNameOrNo.trim())) {
			return new MsgEmity(false, "登录名不能为空！", 8000);
		}
		if (null == sPass || "".equals(sPass.trim())) {
			return new MsgEmity(false, "密码不能为空！", 8001);
		}
		
		MsgEmity me = loginDao.findByNameOrNo(sNameOrNo.trim());
		if (!me.isSuccess()) {
			return new MsgEmity(false, "查询失败，请稍后重试！", 8002);
		}
		
		List<LoginUser> list = me.getData();
		
		if (list.size() < 1) {
			return new MsgEmity(false, "相关登录名的用户不存在！", 8003);
		}
		
		if (list.size() != 1) {
			return new MsgEmity(false, "存在重名用户，请使用工号登录！", 8004);
		}
		
		LoginUser loginUser = list.get(0);
//		if (!MD5Util.md5(sPass, appMd5Key).equals(loginUser.getsPass())) {
//			Log.debug("用户“", loginUser.getsName(), "”登录时密码错误");
//			return new MsgEmity(false, "用户密码错误！", 8005);
//		}
		if (!sPass.equals(loginUser.getsPass())) {
			Log.debug("用户“", loginUser.getsName(), "”登录时密码错误");
			return new MsgEmity(false, "用户密码错误！", 8005);
		}
		
		if (loginUser.getiState() < 1) {
			return new MsgEmity(false, "登录受限，请联系管理员！", 8006);
		}
		
		//--剔除旧的登录用户--//
		String sId = loginUser.getsId();
		Iterator<Map.Entry<String, LoginUser>> it = mapLoginUser.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<String, LoginUser> entry = it.next();
			LoginUser obj = entry.getValue();
			if (obj.getsId().equals(sId)) {
				it.remove();
				Log.debug("旧登录用户'", sId, "'因同账号登录被剔除登录集合!");
			}
		}
		
		String sCookie = MD5Util.md5(sId, "|", LocalDateTime.now().toString(), "|", appMd5Key);
		loginUser.setsCookie(sCookie);//分配的Cookie
		mapLoginUser.put(sCookie, loginUser);
		
		changeCache();//修改缓存
		
		return new MsgEmity(true, "登录成功", sCookie);
	}

	/**
	 * 登出
	 * @param sCookie
	 * @return
	 */
	@Override
	public MsgEmity out(String sCookie) {
		if (!mapLoginUser.containsKey(sCookie)) {
			changeCache();//修改缓存
			return new MsgEmity(true, "已经登出", 8001);
		}
		
		LoginUser login = mapLoginUser.remove(sCookie);
		Log.debug("登录用户'", login.getsId(), "'主动进行了登出操作!");
		checkOutTime();//检查超时的登录用户,超时则剔除集合
		
		return new MsgEmity(true, "登出成功", 8999);
	}
	
	/**
	 * 判断sCookie是否已经登录
	 * @param sCookie 令牌
	 * @return
	 */
	@Override
	public MsgEmity check(String sCookie) {
		LoginUser loginUser = mapLoginUser.get(sCookie);
		if (null == loginUser) {
			return new MsgEmity(false, "该登录令牌信息失效", 8001);
		}
		
		loginUser.setdLastDate(System.currentTimeMillis());
		
		checkOutTime();//检查超时的登录用户,超时则剔除集合
		
		return new MsgEmity(true, "该登录令牌信息有效", 8999);
	}
	
	/**
	 * 登录心跳操作,sCookie存在则更新并返回true,没有则返回false
	 * @param sCookie 令牌
	 * @return
	 */
	@Override
	public MsgEmity heartbeat(String sCookie) {
		if (!mapLoginUser.containsKey(sCookie)) {
			return new MsgEmity(false, "令牌已失效,请重新登录", 8001);
		}
		
		LoginUser loginUser = mapLoginUser.get(sCookie);
		
		loginUser.setdLastDate(System.currentTimeMillis());
		
		checkOutTime();//检查超时的登录用户,超时则剔除集合
		
		return new MsgEmity(true, "令牌心跳已经更新", 8999);
	}
	
	/**
	 * 检查超时的登录用户,超时则剔除集合
	 */
	private synchronized void checkOutTime() {
		if (0 == dLastClearTime) {
			dLastClearTime = System.currentTimeMillis();
			return;
		}

		//--判断是否需要进行清理,注意:这个算法并不严谨,但判断用户超时并不需要很严谨的进行时刻清理--//
		long vNow = System.currentTimeMillis();
		if ((vNow - dLastClearTime) < timeOut()) {
			return;
		}
		
		Iterator<Map.Entry<String, LoginUser>> it = mapLoginUser.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<String, LoginUser> entry = it.next();
			LoginUser loginUser = entry.getValue();
			if ((vNow - loginUser.getdLastDate()) > iTimeOut) {
				it.remove();
				Log.debug("登录用户'", loginUser.getsId(), "'因超时被剔除登录集合!");
			}
		}
		
		changeCache();//修改缓存
		
		dLastClearTime = System.currentTimeMillis();//更新最后清理时间
	}
	
	/**
	 * 取超时时间
	 * @return
	 */
	private long timeOut() {
		if (-1 != iTimeOut) {
			return iTimeOut;
		}
		
		iTimeOut = IntegerUtil.strToInt(serverSessionTimeout, 90) * 1000;
		
		return iTimeOut;
	}

	/**
	 * 取登录信息
	 * @param sCookie
	 * @return
	 */
	@Override
	public MsgEmity getLogin(String sCookie) {
		if (!mapLoginUser.containsKey(sCookie)) {
			return new MsgEmity(false, "令牌已失效,请重新登录", 8001);
		}
		
		LoginUser loginUser = mapLoginUser.get(sCookie);
		LoginUser result = loginUser.copy();
		result.setsPass(null);//必须清除
		
		return new MsgEmity(true, "找到令牌对应的登录信息", result);
	}
	
	/**
	 * 修改缓存
	 */
	private void changeCache() {
		if (null != isDisableTxtCache && isDisableTxtCache) {
			return;
		}
		
		if (null == mapLoginUser || mapLoginUser.size() < 1) {
			return;
		}
		
		Runnable runnable = () -> {
			String filePath = String.format("%sLoginCache.txt", sCacheFilePath);
			
			ObjectMapper mapper = new ObjectMapper();
			try {
				String json = mapper.writeValueAsString(mapLoginUser);
				FileUtil.saveStr(filePath, json);
			} catch (Exception e) {
				Log.error("保存用户登录缓存发生异常：", e.getMessage());
			}
		};
		
		new Thread(runnable).start();
	}
	
}
